"""
App Settings.

Based on code from: http://code.djangoproject.com/ticket/1371
"""
from django.utils.functional import memoize
from django.core.exceptions import ImproperlyConfigured


__all__ = ['RequiredSetting', 'FromSetting', 'ComputedSetting', 'AppSettings']

class SpecialConf(object):
    """
    Base for classes defining special configuration options
    The interface is simple: get_value(name) will be called if
    no value was supplied for the setting by the user 
    """
    def get_value(self, name):
        raise NotImplementedError

    def set_settings_object(self, obj):
        self.settings_object = obj


class RequiredSetting(SpecialConf):
    "Just to mark a setting as required"
    def get_value(self, name):
        raise ImproperlyConfigured, '%s setting is missing' % name


class FromSetting(SpecialConf):
    """ Take default value from another setting """
    def __init__(self, source):
        self.source = source
    def get_value(self, name):
        return getattr(self.settings_object, self.source)


class ComputedSetting(SpecialConf):
    """ 
    Compute default from a set of other settings.
    
    Initialized with a function, a sequence of settings names, and optional
    additional (constant) arguments and keyword arguments. The default for 
    the setting will be computed (only if needed) by calling the function
    with the setting values as first positional arguments, followed by
    """
    def __init__(self, func, sources, *args, **kwargs):
        self.func = func
        if isinstance(sources, basestring):
            sources = (sources,)
        self.sources = sources
        self.args = args
        self.kwargs = kwargs

    def get_value(self, name):
        sources = [getattr(self.settings_object, s) for s in self.sources]
        sources.extend(self.args)
        return self.func(*sources, **self.kwargs)


class SettingProperty(object):
    def __init__(self, name, default, cache):
        self.name = name
        self.default = default
        self.getter = memoize(self.get_conf_value, cache, 1)
        
    def __get__(self, settings_object, type=None):
        return self.getter(self.name, self.default, settings_object)

    @staticmethod
    def get_conf_value(name, default, settings_object):
        # First look in the project settings file
        from django.conf import settings
        value = getattr(settings, name, default)
        if isinstance(value, SpecialConf):
            # Special settings may want to use other settings from the settings_object
            value.set_settings_object(settings_object)
            value = value.get_value(name)
        return value


class SettingsMetaClass(type):
    def __new__(meta, name, bases, namespace):
        # turn all all-caps names into settings
        cache = {}
        for n, v in namespace.items():
            if n==n.upper():
                namespace[n] = SettingProperty(n, v, cache)
        return super(SettingsMetaClass, meta).__new__(meta, name, bases, namespace)
 

class AppSettings(object):
    __metaclass__ = SettingsMetaClass

    def __getattr__(self, name):
        # for names not in our class
        from django.conf import settings
        return getattr(settings, name)
